module(..., package.seeall)

require("lfs")
require("svn")

SVNVersium = {}

----------------------------------------------------------------------------
-- An auxiliary function for opening files "safely" (i.e., with error
-- messages.)
-- 
-- @param path       the file path.
-- @param mode       the access mode ("r", "w", "a", etc.)
-- @return           a file handle
----------------------------------------------------------------------------

local function _open_file(path, mode, strict)
   local f = io.open(path, mode)
   if (not f) and strict then
      error("\nFile Versium: Can't open file:" .. path .. 
	    ", mode = ".. (mode or "r"))
   end
   return f
end

----------------------------------------------------------------------------
-- An auxiliary function for writing to a file.  Eventually this will
-- do atomic write.
-- 
-- @param path       the file path.
-- @param data       data to be written
-- @return           nothing
----------------------------------------------------------------------------

local function _write_file(path, data)
   local f = _open_file(path, "w", true)
   f:write(data)
   f:close()
end


----------------------------------------------------------------------------
-- An auxiliary function for reading the content of a file.
-- 
-- @param path       the file path.
-- @return           a string
----------------------------------------------------------------------------

local function _read_file(path)
   local f = _open_file(path, "r", true)
   local data = f:read("*all")
   f:close()
   return data
end

----------------------------------------------------------------------------
-- An auxiliary function for reading the content of a file without
-- complaining if it doesn't exist.
-- 
-- @param path       the file path.
-- @return           a string
----------------------------------------------------------------------------

local function _read_file_if_exists(path)
   local f = _open_file(path, "r", false)
   if f then
      local data = f:read("*all")
      f:close()
      return data
   else
      return ""
   end
end

----------------------------------------------------------------------------
-- Instantiates a new simple versium object.
-- 
-- @param impl       the implementation module.
-- @param params     the parameters to pass to the implementation.
-- @return           a new versium object.
----------------------------------------------------------------------------

function SVNVersium.new(self, params, versium)
   local reposurl = params.reposurl
   local wc = params.wc
   local obj = {reposurl=reposurl, wc = wc, versium=versium}
   svn.checkout (reposurl, wc)
   build_node_table (obj)
   setmetatable(obj, self)
   self.__index = self
   return obj         
end


----------------------------------------------------------------------------
-- Returns a table representing the node with a given id.
--
-- @param version    the desired version of the node (defaults to current).
-- @return           the node as a string.
----------------------------------------------------------------------------

function SVNVersium.get_node(self, id, version)
   local history = self:get_node_history(id)
   local node
   if version then
      node = history[#history-tonumber(version)+1]
   else
      node = history[1]
   end
   node.data = svn.cat (self.wc.."/"..id, self.node_table [id][version])
   return node
end


----------------------------------------------------------------------------
-- Returns true or false depending on whether the node with this id
-- exists.
-- 
-- @param id         an id of an node.
-- @return           true or false.
----------------------------------------------------------------------------

function SVNVersium.node_exists (self, id)
	return self.node_table [id]
end

----------------------------------------------------------------------------
-- Returns a table with the metadata for the latest version of the
-- node, same as get_history(id)[1] but potentially cheaper in case of
-- other implementations.
-- 
-- @param id         an id of an node.
-- @return           the metadata for the latest version or nil.
----------------------------------------------------------------------------

function SVNVersium.get_node_info (self, id)
   return self:get_history(id)[1]
end


----------------------------------------------------------------------------
-- Returns a list of ids of all nodes in the repository, in no
-- particular order.
-- 
-- @return           a list of IDs
----------------------------------------------------------------------------

function SVNVersium.get_node_ids (self)
   local t1 =  svn.list (self.reposurl)
   local t2 = {}
   for _, v in pairs (t1) do
      t2 [#t2 + 1] = v
   end
   return t2
end

----------------------------------------------------------------------------
-- Saves a new version of the node.
--
-- @param id         the id of the node.
-- @param data       the value to save.
-- @param author     the user name to be associated with the change.
-- @param comment    the change comment.
-- @param extra      any extra metadata
-- @return           the version of ID of the new node.
----------------------------------------------------------------------------

function SVNVersium.save_version (self, id, data, author, comment, extra)
   local node_path = self.wc.."/"..id
  
   _write_file(node_path, data)

   local history, raw_history
   local new_version_id

   -- adds the file to repository if necessary
   if not self:node_exists(id) then
	  svn.add (node_path)
	  new_version_id = string.format ("%06d", 1)
	  self.node_table [id] = {}
   else
      history, raw_history = self:get_node_history(id)
	  new_version_id = string.format("%06d", #history + 1)
   end

   local t = os.date("*t")
   local timestamp = string.format("%02d-%02d-%02d %02d:%02d:%02d", 
				   t.year, t.month, t.day,
				   t.hour, t.min, t.sec)

   svn.propset (node_path, "versium:version", self.versium:longquote (new_version_id))
   svn.propset (node_path, "versium:timestamp", self.versium:longquote (timestamp))
   svn.propset (node_path, "versium:author", self.versium:longquote (author))
   svn.propset (node_path, "versium:comment", self.versium:longquote (comment))

   local rev = svn.commit (self.wc)
   
   self.node_table [id][new_version_id] = rev

   svn.update (self.wc)

   return new_version_id
end


----------------------------------------------------------------------------
-- Returns the history of the node as a list of tables, each
-- representing a revision of the node (with fields like version,
-- author, comment, and extra).  The version can be filtered by a time
-- prefix.
--
-- @param id         the id of the node.
-- @param prefix     time prefix.
-- @return           a list of tables representing the versions and the 
--                   raw prepresentation of nodes history (as lua code).
----------------------------------------------------------------------------

function SVNVersium.get_node_history (self, id, prefix)
   local node_path = self.wc .. "/" .. id
   local raw_history = ""
   local log = svn.log (node_path)
   
   for i, _ in pairs (log) do
      local s = "add_version{"
	  local prop = svn.proplist (node_path, i)
	  prop = prop [self.reposurl .. "/" .. id]
	  s = s .. "\n version = " .. prop ["versium:version"]
	  s = s .. ",\n timestamp = " .. prop ["versium:timestamp"]
	  s = s .. ",\n author = " .. prop ["versium:author"]
	  s = s .. ",\n comment = " .. prop ["versium:comment"] .. ",\n}\n"
	  raw_history = s .. raw_history
   end

   local all_versions = {}

   local env = self.versium:load_lua(raw_history, 
   				     { add_version = function (values)
   					  table.insert(all_versions, values)
   				       end } )
   -- ::TODO:: add support for prefix
  
   return all_versions, raw_history
end


----------------------------------------------------------------------------
-- Instantiate a new simple versium object.
-- 
-- @param impl       the implementation module.
-- @param params     the parameters to pass to the implementation.
-- @return           a new versium object.
----------------------------------------------------------------------------

function open(params, versium)
   return SVNVersium:new(params, versium)
end


function fileName (path)
	local n = #path
	for i=n, 1, -1 do
		if string.sub (path, i, i) == '/' then
			return string.sub (path, i+1, n)
		end
	end
end

function build_node_table (obj)
	local node_table = {}
	local wc = obj.wc

	--gets the list of the files
	local t = svn.list (wc)
	--number of the last revision
	local n = 0

	for file, r in pairs (t) do
		node_table[file] = {}
		if r > n then
			n = r
		end
	end

	--get the "versium:version" property of the files in each revision, so
	--we can associate the Versium revision number with the SVN revision number
	for i=n, 1, -1 do
		local prop = svn.propget (wc, "versium:version", i)
        for path, v in pairs (prop) do
			local s = loadstring ('return ' .. v) ()
        	node_table [fileName (path)][s] = i
		end
	end

	obj.node_table = node_table


end
